From 51bdda10898ef72ef2f3bb38552d2a878a8c2f55 Mon Sep 17 00:00:00 2001 From: robertl Date: Wed, 4 Feb 2009 23:40:47 +0000 Subject: [PATCH] Bring in the KML writer from Google Earth 5.0. Set the extents of the time sliders automatically. --- defs.h | 2 + kml.c | 224 +++++++++++++++++++++------------- reference/earth-expertgps.kml | 13 +- reference/earth-gc.kml | 13 +- 4 files changed, 163 insertions(+), 89 deletions(-) diff --git a/defs.h b/defs.h index 984c7c6f4..4c8ea1beb 100644 --- a/defs.h +++ b/defs.h @@ -542,6 +542,8 @@ void waypt_init_bounds(bounds *bounds); int waypt_bounds_valid(bounds *bounds); void waypt_add_to_bounds(bounds *bounds, const waypoint *waypointp); void waypt_compute_bounds(bounds *); +double gcgeodist(const double lat1, const double lon1, + const double lat2, const double lon2); void waypt_flush(queue *); void waypt_flush_all(void); unsigned int waypt_count(void); diff --git a/kml.c b/kml.c index 0de45c600..b3847c263 100644 --- a/kml.c +++ b/kml.c @@ -1,7 +1,8 @@ -/* +/* Support for Google Earth & Keyhole "kml" format. - Copyright (C) 2005, 2006, 2007 Robert Lipe, robertlipe@usa.net + Copyright (C) 2005, 2006, 2007, 2008, 2009 Robert Lipe, + robertlipe@gpsbabel.org Updates by Andrew Kirmse, akirmse at google.com This program is free software; you can redistribute it and/or modify @@ -76,6 +77,9 @@ static int point3d_list_len; static point3d *point3d_list; static int realtime_positioning; static int do_indentation = 1; +static bounds kml_bounds; +static time_t kml_time_min; +static time_t kml_time_max; #define TD(FMT,DATA) kml_write_xml(0, "" FMT " \n", DATA) #define TD2(FMT,DATA, DATA2) kml_write_xml(0, "" FMT " \n", DATA, DATA2) @@ -83,49 +87,40 @@ static int do_indentation = 1; // Icons provided and hosted by Google. Used with permission. #define ICON_BASE "http://earth.google.com/images/kml-icons/" -static const char kml22_hdr[] = - "\n"; -// No "baked in" schemaLocation, per Google recommendation. -// "\txsi:schemaLocation=\"http://earth.google.com/kml/2.2 \n" -// "\thttp://code.google.com/apis/kml/schema/kml22beta.xsd\">\n"; - -static const char kml21_hdr[] = - "\n"; -// No "baked in" schemaLocation, per Google recommendation. -// "\txsi:schemaLocation=\"http://earth.google.com/kml/2.1 \n" -// "\thttp://code.google.com/apis/kml/schema/kml21.xsd\">\n"; +static const char kml22_hdr[] = + "\n"; + static arglist_t kml_args[] = { {"deficon", &opt_deficon, "Default icon name", NULL, ARGTYPE_STRING, ARG_NOMINMAX }, - {"lines", &opt_export_lines, + {"lines", &opt_export_lines, "Export linestrings for tracks and routes", "1", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"points", &opt_export_points, + {"points", &opt_export_points, "Export placemarks for tracks and routes", "1", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"line_width", &opt_line_width, + {"line_width", &opt_line_width, "Width of lines, in pixels", "6", ARGTYPE_INT, ARG_NOMINMAX }, - {"line_color", &opt_line_color, + {"line_color", &opt_line_color, "Line color, specified in hex AABBGGRR", "99ffac59", ARGTYPE_STRING, ARG_NOMINMAX }, - {"floating", &opt_floating, - "Altitudes are absolute and not clamped to ground", + {"floating", &opt_floating, + "Altitudes are absolute and not clamped to ground", "0", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"extrude", &opt_extrude, - "Draw extrusion line from trackpoint to ground", + {"extrude", &opt_extrude, + "Draw extrusion line from trackpoint to ground", "0", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"trackdata", &opt_trackdata, - "Include extended data for trackpoints (default = 1)", + {"trackdata", &opt_trackdata, + "Include extended data for trackpoints (default = 1)", "1", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"trackdirection", &opt_trackdirection, - "Indicate direction of travel in track icons (default = 0)", + {"trackdirection", &opt_trackdirection, + "Indicate direction of travel in track icons (default = 0)", "0", ARGTYPE_BOOL, ARG_NOMINMAX }, - {"units", &opt_units, - "Units used when writing comments ('s'tatute or 'm'etric)", + {"units", &opt_units, + "Units used when writing comments ('s'tatute or 'm'etric)", "s", ARGTYPE_STRING, ARG_NOMINMAX }, {"labels", &opt_labels, "Display labels on track and routepoints (default = 1)", @@ -136,7 +131,7 @@ arglist_t kml_args[] = { ARG_TERMINATOR }; -static +static struct { int freshness; const char *icon; @@ -146,10 +141,10 @@ struct { { 0, ICON_BASE "youarehere-0.png" }, // Green }; -#define ICON_NOSAT ICON_BASE "youarehere-warning.png"; +#define ICON_NOSAT ICON_BASE "youarehere-warning.png"; #define ICON_WPT "http://maps.google.com/mapfiles/kml/pal4/icon61.png" #define ICON_TRK ICON_BASE "track-directional/track-none.png" -#define ICON_RTE ICON_BASE "track-directional/track-none.png" +#define ICON_RTE ICON_BASE "track-directional/track-none.png" #define ICON_DIR ICON_BASE "track-directional/track-%d.png" // format string where next arg is rotational degrees. #define MYNAME "kml" @@ -170,7 +165,7 @@ kml_read(void) static xg_callback wpt_s, wpt_e; static xg_callback wpt_name, wpt_desc, wpt_coord, wpt_icon, trk_coord, wpt_time; -static +static xg_tag_mapping kml_map[] = { { wpt_s, cb_start, "/Placemark" }, { wpt_e, cb_end, "/Placemark" }, @@ -188,7 +183,7 @@ xg_tag_mapping kml_map[] = { { NULL, 0, NULL } }; -static +static const char * kml_tags_to_ignore[] = { "kml", "Document", @@ -196,8 +191,8 @@ const char * kml_tags_to_ignore[] = { NULL, }; -void wpt_s(const char *args, const char **unused) -{ +void wpt_s(const char *args, const char **unused) +{ wpt_tmp = waypt_new(); wpt_tmp_queued = 0; } @@ -272,7 +267,7 @@ void trk_coord(const char *args, const char **attrv) } } -static +static void kml_rd_init(const char *fname) { @@ -298,10 +293,13 @@ static void kml_wr_init(const char *fname) { char u = 's'; + waypt_init_bounds(&kml_bounds); + kml_time_min = 0; + kml_time_max = 0; if (opt_units) { u = tolower(opt_units[0]); - } + } switch(u) { case 's': fmt_setunits(units_statute); break; @@ -314,7 +312,7 @@ kml_wr_init(const char *fname) ofd = gbfopen(fname, "w", MYNAME); } -/* +/* * The magic here is to try to ensure that posnfilename is atomically * updated. */ @@ -406,10 +404,10 @@ kml_write_xmle(const char *tag, const char *v) } #define hovertag(h) h ? 'h' : 'n' -static void kml_write_bitmap_style_(const char *style, const char * bitmap, +static void kml_write_bitmap_style_(const char *style, const char * bitmap, int highlighted, int force_heading) { - kml_write_xml(0, "\n", + kml_write_xml(0, "\n", highlighted ? "Highlighted" : "Normal", style); kml_write_xml(1, "\n"); -} +} /* A wrapper for the above function to emit both a highlighted * and non-highlighted version of the style to allow the icons @@ -465,7 +463,7 @@ static void kml_output_timestamp(const waypoint *waypointp) if (waypointp->creation_time) { xml_fill_in_time(time_string, waypointp->creation_time, waypointp->microseconds, XML_LONG_TIME); if (time_string[0]) { - kml_write_xml(0, "%s\n", + kml_write_xml(0, "%s\n", time_string); } } @@ -474,7 +472,7 @@ static void kml_output_timestamp(const waypoint *waypointp) /* * Output the track summary. */ -static +static void kml_output_trkdescription(const route_head *header, computed_trkdata *td) { char *max_alt_units; @@ -565,7 +563,7 @@ void kml_output_trkdescription(const route_head *header, computed_trkdata *td) } -static +static void kml_output_header(const route_head *header, computed_trkdata*td) { if (!realtime_positioning) { @@ -631,13 +629,13 @@ static void kml_output_description(const waypoint *pt) TD2("Speed: %.1f %s", spd, spd_units); } if WAYPT_HAS(pt, course) TD("Heading: %.1f", pt->course); - /* This really shouldn't be here, but as of this writing, - * Earth can't edit/display the TimeStamp. + /* This really shouldn't be here, but as of this writing, + * Earth can't edit/display the TimeStamp. */ if (pt->creation_time) { char time_string[64]; - xml_fill_in_time(time_string, pt->creation_time, + xml_fill_in_time(time_string, pt->creation_time, pt->microseconds, XML_LONG_TIME); if (time_string[0]) { TD("Time: %s", time_string); @@ -648,6 +646,18 @@ static void kml_output_description(const waypoint *pt) kml_write_xml(-1, "]]>\n"); } +static void kml_recompute_time_bounds(const waypoint *waypointp) { + if (waypointp->creation_time && (waypointp->creation_time < kml_time_min)) { + kml_time_min = waypointp->creation_time; + } + if (waypointp->creation_time > kml_time_max) { + kml_time_max = waypointp->creation_time; + if (kml_time_min == 0) { + kml_time_min = waypointp->creation_time; + } + } +} + static void kml_output_point(const waypoint *waypointp, kml_point_type pt_type) { const char *style; @@ -655,6 +665,9 @@ static void kml_output_point(const waypoint *waypointp, kml_point_type pt_type) point3d *pt = &point3d_list[point3d_list_len]; point3d_list_len++; + waypt_add_to_bounds(&kml_bounds, waypointp); + kml_recompute_time_bounds(waypointp); + switch (pt_type) { case kmlpt_track: style = "#track"; break; case kmlpt_route: style = "#route"; break; @@ -693,10 +706,10 @@ static void kml_output_point(const waypoint *waypointp, kml_point_type pt_type) } else { if (trackdirection && (pt_type == kmlpt_track)) { char buf[100]; - if (waypointp->speed < 1) + if (waypointp->speed < 1) snprintf(buf, sizeof(buf), "%s-none", style); else - snprintf(buf, sizeof(buf), "%s-%d", style, + snprintf(buf, sizeof(buf), "%s-%d", style, (int) (waypointp->course / 22.5 + .5) % 16); kml_write_xml(0, "%s\n", buf); } else { @@ -728,7 +741,7 @@ static void kml_output_tailer(const route_head *header) if (export_points && point3d_list_len > 0) { kml_write_xml(-1, "\n"); } - + // Add a linestring for this track? if (export_lines && point3d_list_len > 0) { kml_write_xml(1, "\n"); @@ -755,19 +768,19 @@ static void kml_output_tailer(const route_head *header) kml_write_xml(0, "1\n"); kml_write_xml(1, "\n"); for (i = 0; i < point3d_list_len; ++i) - kml_write_xml(0, "%f,%f,%f\n", + kml_write_xml(0, "%f,%f,%f\n", point3d_list[i].longitude, point3d_list[i].latitude, point3d_list[i].altitude); - + kml_write_xml(-1, "\n"); kml_write_xml(-1, "\n"); kml_write_xml(-1, "\n"); } - + xfree(point3d_list); point3d_list = NULL; - + if (!realtime_positioning) { kml_write_xml(-1, "\n"); } @@ -776,8 +789,8 @@ static void kml_output_tailer(const route_head *header) /* * Completely different writer for geocaches. */ -static -void kml_gc_make_ballonstyle(void) +static +void kml_gc_make_ballonstyle(void) { // BalloonStyle for Geocaches. kml_write_xml(1, "\n"); } -static +static char * kml_lookup_gc_icon(const waypoint *waypointp) { @@ -865,7 +878,6 @@ static void kml_geocache_pr(const waypoint *waypointp) char *p, *is; double lat = waypointp->latitude; double lng = waypointp->longitude; -// optionally "fuzz" lat/lng here. kml_write_xml(1, "\n"); @@ -913,7 +925,7 @@ static void kml_geocache_pr(const waypoint *waypointp) kml_write_xml(0, "%s\n", kml_lookup_gc_container(waypointp)); - // Highlight any issues with the cache, such as temp unavail + // Highlight any issues with the cache, such as temp unavail // or archived. kml_write_xml(0, ""); if (waypointp->gc_data->is_archived == status_true) { @@ -958,6 +970,8 @@ static void kml_waypt_pr(const waypoint *waypointp) kml_write_xml(-1, "\n"); } #endif + waypt_add_to_bounds(&kml_bounds, waypointp); + kml_recompute_time_bounds(waypointp); if (waypointp->gc_data->diff && waypointp->gc_data->terr) { kml_geocache_pr(waypointp); @@ -1014,7 +1028,7 @@ static void kml_waypt_pr(const waypoint *waypointp) } kml_write_xml(0, "%f,%f,%f\n", - waypointp->longitude, waypointp->latitude, + waypointp->longitude, waypointp->latitude, waypointp->altitude == unknown_alt ? 0.0 : waypointp->altitude); kml_write_xml(-1, "\n"); @@ -1025,7 +1039,7 @@ static void kml_waypt_pr(const waypoint *waypointp) * TRACKPOINTS */ -static void kml_track_hdr(const route_head *header) +static void kml_track_hdr(const route_head *header) { computed_trkdata *td; track_recompute(header, &td); @@ -1038,7 +1052,7 @@ static void kml_track_disp(const waypoint *waypointp) kml_output_point(waypointp, kmlpt_track); } -static void kml_track_tlr(const route_head *header) +static void kml_track_tlr(const route_head *header) { kml_output_tailer(header); } @@ -1047,7 +1061,7 @@ static void kml_track_tlr(const route_head *header) * ROUTES */ -static void kml_route_hdr(const route_head *header) +static void kml_route_hdr(const route_head *header) { kml_output_header(header, NULL); } @@ -1057,11 +1071,61 @@ static void kml_route_disp(const waypoint *waypointp) kml_output_point(waypointp, kmlpt_route); } -static void kml_route_tlr(const route_head *header) +static void kml_route_tlr(const route_head *header) { kml_output_tailer(header); } +// For Earth 5.0, we write a LookAt that encompasses +// the bounding box of our entire data set and set the event times +// to include all our data. +void kml_write_AbstractView(void) { + kml_write_xml(1, "\n"); + + if (kml_time_min || kml_time_max) { + kml_write_xml(1, "\n"); + if (kml_time_min) { + char time_string[64]; + xml_fill_in_time(time_string, kml_time_min, 0, XML_LONG_TIME); + if (time_string[0]) { + kml_write_xml(0, "%s\n", time_string); + } + } + if (kml_time_max) { + char time_string[64]; + xml_fill_in_time(time_string, kml_time_max, 0, XML_LONG_TIME); + if (time_string[0]) { + kml_write_xml(0, "%s\n", time_string); + } + } + kml_write_xml(-1, "\n"); + } + + // If our BB spans the antemeridian, flip sign on one. + // This doesn't make our BB optimal, but it at least prevents us from + // zooming to the wrong hemisphere. + if (kml_bounds.min_lon * kml_bounds.max_lon < 0) { + kml_bounds.min_lon = -kml_bounds.max_lon; + } + + kml_write_xml(0, "%f\n", + (kml_bounds.min_lon + kml_bounds.max_lon) / 2); + kml_write_xml(0, "%f\n", + (kml_bounds.min_lat + kml_bounds.max_lat) / 2); + + // It turns out the length of the diagonal of the bounding box gives us a + // reasonable guess for setting the camera altitude. + double bb_size = gcgeodist(kml_bounds.min_lat, kml_bounds.min_lon, + kml_bounds.max_lat, kml_bounds.max_lon); + // Clamp bottom zoom level. Otherwise, a single point zooms to grass. + if (bb_size < 1000) { + bb_size = 1000; + } + kml_write_xml(0, "%f\n", bb_size * 1.3); + + kml_write_xml(-1, "\n"); +} + void kml_write(void) { char import_time[100]; @@ -1077,24 +1141,13 @@ void kml_write(void) kml_write_xml(0, "\n"); - /* - * This is a bit cowardly. Our geocache writer takes advantage - * of KML 2.2 features present only in Earth 4.2 and newer. The - * output is actually compatible with Earth versions back to 4.0 - * for the non-geocaching case, so we'll be conservative and not - * output the new namespace if we don't need it. - */ - if (geocaches_present) { - kml_write_xml(1, kml22_hdr); - } else { - kml_write_xml(1, kml21_hdr); - } + kml_write_xml(1, kml22_hdr); kml_write_xml(1, "\n"); now = current_time(); strftime(import_time, sizeof(import_time), "%c", localtime(&now)); - if (realtime_positioning) + if (realtime_positioning) kml_write_xml(0, "GPS position\n"); else kml_write_xml(0, "GPS device\n"); @@ -1126,7 +1179,7 @@ void kml_write(void) } kml_write_bitmap_style(kmlpt_waypoint, ICON_WPT, NULL); - + if (track_waypt_count() || route_waypt_count()) { // Style settings for line strings kml_write_xml(1, "